﻿using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Text;
using System.Windows.Media.Imaging;
using STEM123.StemService;

namespace STEM123
{
    /// <summary>
    /// Used by UI elements to maintain the state related to explain view of application, i.e.
    /// slides being created, term being explained and associated content.
    /// </summary>
    public class ExplainViewModel : IViewModel
    {
        #region Private members

        /// <summary>
        /// Maximum number of slides.
        /// </summary>
        private const uint MaximumSlides = 3;

        /// <summary>
        /// Format to be used when naming temporary files on local storage.
        /// </summary>
        private const string NameOfAnswerTempFile = "{0}_{1}_t.{2}"; // term, slide index, file extension

        /// <summary>
        /// Format to be used when naming final files on local storage.
        /// </summary>
        private const string NameOfAnswerFile = "{0}_{1}.{2}"; // term, slide index, file extension

        /// <summary>
        /// Initializes a new instance of ExplainViewModel.
        /// </summary>
        private ExplainViewModel()
        {
            Slides = new LocalSlide[MaximumSlides];
            Slides[0] = new LocalSlide();
            Slides[1] = new LocalSlide();
            Slides[2] = new LocalSlide();
        }

        /// <summary>
        /// Retrieves name of file given its content type and whether it is temporary or not.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="temporary">True if file is temporary else false.</param>
        /// <param name="fileName">Name of file.</param>
        /// <param name="appStorage">Instance of local app storage.</param>
        /// <param name="contentType">Type of content.</param>
        private void getFileStorageInfo(uint slideIndex, bool temporary, out string fileName, out IsolatedStorageFile appStorage, ContentTypes contentType)
        {
            string extension = contentType.ToString().Replace("JPEG", "JPG").ToLowerInvariant();

            string tempFileName = string.Format(NameOfAnswerTempFile, Term, slideIndex, extension);
            string savedFileName = string.Format(NameOfAnswerFile, Term, slideIndex, extension);

            fileName = temporary ? tempFileName : savedFileName;

            appStorage = IsolatedStorageFile.GetUserStoreForApplication();
        }

        #endregion

        #region Public members

        /// <summary>
        /// Static constructor.
        /// </summary>
        static ExplainViewModel()
        {
            Instance = new ExplainViewModel();
        }

        /// <summary>
        /// Retrieves raw name of file from URI.
        /// </summary>
        /// <param name="fileUri">Uri of file.</param>
        /// <returns>File name extracted from uri.</returns>
        public static string GetFileName(Uri fileUri)
        {
            return fileUri.ToString().Replace("file://", string.Empty).Replace("/", string.Empty);
        }

        /// <summary>
        /// Term being described in current presentation being created / edited.
        /// </summary>
        public string Term { get; set; }

        /// <summary>
        /// Slides being created.
        /// </summary>
        public Slide[] Slides { get; private set; }

        /// <summary>
        /// Instance of explain view model.
        /// </summary>
        public static ExplainViewModel Instance { get; private set; }

        /// <summary>
        /// Resets state of explain view model.
        /// </summary>
        public void ResetState()
        {
            Term = string.Empty;

            Slides = new LocalSlide[MaximumSlides];
            Slides[0] = new LocalSlide();
            Slides[1] = new LocalSlide();
            Slides[2] = new LocalSlide();
        }

        /// <summary>
        /// Validates content based on certain rules
        /// and returns error messages if any.
        /// </summary>
        /// <returns>Error messages, if any.</returns>
        public string validateContent()
        {
            StringBuilder messages = new StringBuilder();
            uint nonEmptySlides = 0;

            for (int i = 0; i < Slides.Length; i++)
            {
                LocalSlide slide = Slides[i] as LocalSlide;

                if (!string.IsNullOrEmpty(slide.Question))
                {
                    nonEmptySlides++;

                    if (slide.MediaType == MediaTypes.TextOnly && string.IsNullOrEmpty(slide.Answer))
                    {
                        messages.AppendLine("Slide " + (i + 1) + ": No answer content is present.");
                    }
                    else if (slide.MediaType == MediaTypes.TextWithImage && slide.LocalImageSource == null)
                    {
                        messages.AppendLine("Slide " + (i + 1) + ": No image was specified for answer.");
                    }
                    else if ((slide.MediaType == MediaTypes.AudioOnly || slide.MediaType == MediaTypes.AudioWithImage) && slide.MediaDuration == uint.MinValue)
                    {
                        messages.AppendLine("Slide " + (i + 1) + ": No audio was recorded for answer.");
                    }
                    else if (slide.MediaType == MediaTypes.Video && slide.MediaDuration == uint.MinValue)
                    {
                        messages.AppendLine("Slide " + (i + 1) + ": No video was recorded for answer.");
                    }
                }
            }

            if (nonEmptySlides == 0)
            {
                messages.Clear();
                messages.AppendLine("Please select a question for at least one slide.");
            }

            return messages.ToString();
        }

        /// <summary>
        /// Writes an image to local storage.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="imageStream">Stream of image.</param>
        /// <param name="temporary">True if file is temporary else false.</param>
        /// <returns>Uri of saved file.</returns>
        public Uri WriteImage(uint slideIndex, Stream imageStream, bool temporary)
        {
            string fileName;
            IsolatedStorageFile appStorage;

            getFileStorageInfo(slideIndex, temporary, out fileName, out appStorage, ContentTypes.JPEG);

            using (appStorage)
            {
                using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(fileName, FileMode.Create, appStorage))
                {
                    BitmapImage image = new BitmapImage();
                    image.SetSource(imageStream);
                    WriteableBitmap bitmap = new WriteableBitmap(image);

                    Extensions.SaveJpeg(bitmap, isoFileStream, bitmap.PixelWidth, bitmap.PixelHeight, 0, 90);
                }
            }

            return new Uri(@"\\" + fileName); // Uri does not accept simple file names
        }

        /// <summary>
        /// Reads an image given its file uri.
        /// </summary>
        /// <param name="fileUri">Uri of file.</param>
        /// <returns>Instance of bitmap image.</returns>
        public BitmapImage ReadImage(Uri fileUri)
        {
            BitmapImage image = new BitmapImage();
            IsolatedStorageFile appStorage;

            appStorage = IsolatedStorageFile.GetUserStoreForApplication();

            using (appStorage)
            {
                using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(fileUri.Host, FileMode.Open, appStorage))
                {
                    image.SetSource(isoFileStream);
                }
            }

            return image;
        }

        /// <summary>
        /// Reads an image file given the index of slide.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="temporary">True if temporary file, else false.</param>
        /// <returns>Instance of bitmap image.</returns>
        public BitmapImage ReadImage(uint slideIndex, bool temporary)
        {
            BitmapImage image = new BitmapImage();
            string fileName;
            IsolatedStorageFile appStorage;

            getFileStorageInfo(slideIndex, temporary, out fileName, out appStorage, ContentTypes.JPEG);

            using (appStorage)
            {
                using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(fileName, FileMode.Open, appStorage))
                {
                    image.SetSource(isoFileStream);
                }
            }

            return image;
        }

        /// <summary>
        /// Reads a file as bytes, given its uri.
        /// </summary>
        /// <param name="fileUri">Uri of file.</param>
        /// <returns>Array of bytes containing data of file.</returns>
        public byte[] ReadFile(Uri fileUri)
        {
            byte[] buffer;
            IsolatedStorageFile appStorage;

            appStorage = IsolatedStorageFile.GetUserStoreForApplication();

            using (appStorage)
            {
                using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(fileUri.Host, FileMode.Open, appStorage))
                {
                    buffer = new byte[isoFileStream.Length];
                    isoFileStream.Read(buffer, 0, buffer.Length);
                }
            }

            return buffer;
        }

        /// <summary>
        /// Either commits a temporary file as saved file or gets rid of it.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="commitChanges">True to commit changes else false.</param>
        /// <returns>Uri of finally saved file.</returns>
        public Uri SetImage(uint slideIndex, bool commitChanges)
        {
            Uri finalUri = null;
            string commitFileName, tempFileName;
            IsolatedStorageFile appStorage;

            if (commitChanges)
            {
                // remove previously committed file
                getFileStorageInfo(slideIndex, false, out commitFileName, out appStorage, ContentTypes.JPEG);

                using (appStorage)
                {
                    appStorage.DeleteFile(commitFileName);
                }

                // save temp file as committed file
                getFileStorageInfo(slideIndex, true, out tempFileName, out appStorage, ContentTypes.JPEG);

                using (appStorage)
                {
                    string[] files = appStorage.GetFileNames();

                    appStorage.MoveFile(tempFileName, commitFileName);
                }

                finalUri = new Uri(@"\\" + commitFileName);  // Uri does not accept simple file names
            }
            else
            {
                // remove temp file
                getFileStorageInfo(slideIndex, true, out tempFileName, out appStorage, ContentTypes.JPEG);

                using (appStorage)
                {
                    appStorage.DeleteFile(tempFileName);
                }
            }

            return finalUri;
        }

        /// <summary>
        /// Gets name of video file name, to be saved locally.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="temporary">True if temporary, else false.</param>
        /// <returns>Name of video file to be saved locally.</returns>
        public string GetVideoFileName(uint slideIndex, bool temporary)
        {
            string fileName;
            IsolatedStorageFile appStorage;

            getFileStorageInfo(slideIndex, temporary, out fileName, out appStorage, ContentTypes.MP4);
            appStorage.Dispose();

            return fileName;
        }

        /// <summary>
        /// Reads video from isolated storage.
        /// </summary>
        /// <param name="fileUri">Uri of file.</param>
        /// <returns>Stream of file.</returns>
        public IsolatedStorageFileStream ReadVideo(Uri fileUri)
        {
            IsolatedStorageFile appStorage;
            IsolatedStorageFileStream isoFileStream;

            appStorage = IsolatedStorageFile.GetUserStoreForApplication();

            using (appStorage)
            {
                isoFileStream = new IsolatedStorageFileStream(GetFileName(fileUri), FileMode.Open, appStorage);
            }

            return isoFileStream;
        }

        /// <summary>
        /// Either commits a temporary file as saved file or gets rid of it.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="commitChanges">True to commit changes else false.</param>
        /// <returns>Uri of finally saved file.</returns>
        public Uri SetVideo(uint slideIndex, bool commitChanges)
        {
            Uri finalUri = null;
            string commitFileName, tempFileName;
            IsolatedStorageFile appStorage;

            if (commitChanges)
            {
                // remove previously committed file
                getFileStorageInfo(slideIndex, false, out commitFileName, out appStorage, ContentTypes.MP4);

                using (appStorage)
                {
                    appStorage.DeleteFile(commitFileName);
                }

                // save temp file as committed file
                getFileStorageInfo(slideIndex, true, out tempFileName, out appStorage, ContentTypes.MP4);

                using (appStorage)
                {
                    string[] files = appStorage.GetFileNames();

                    appStorage.MoveFile(tempFileName, commitFileName);
                }

                finalUri = new Uri(@"\\" + commitFileName);  // Uri does not accept simple file names
            }
            else
            {
                // remove temp file
                getFileStorageInfo(slideIndex, true, out tempFileName, out appStorage, ContentTypes.MP4);

                using (appStorage)
                {
                    appStorage.DeleteFile(tempFileName);
                }
            }

            return finalUri;
        }

        /// <summary>
        /// Writes audio to local storage.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="buffer">Buffer containing audio header and data.</param>
        /// <param name="temporary">True if temporary, else false.</param>
        /// <returns>Uri of saved file.</returns>
        public Uri WriteAudio(uint slideIndex, byte[] buffer, bool temporary)
        {
            string fileName;
            IsolatedStorageFile appStorage;

            getFileStorageInfo(slideIndex, temporary, out fileName, out appStorage, ContentTypes.WAV);

            using (appStorage)
            {
                using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(fileName, FileMode.Create, appStorage))
                {
                    isoFileStream.Write(buffer, 0, buffer.Length);
                }
            }

            return new Uri(@"\\" + fileName); // Uri does not accept simple file names
        }

        /// <summary>
        /// Reads an audio file.
        /// </summary>
        /// <param name="fileUri">Uri of file.</param>
        /// <returns>Stream of audio file.</returns>
        public MemoryStream ReadAudio(Uri fileUri)
        {
            IsolatedStorageFile appStorage;
            MemoryStream stream = new MemoryStream();

            appStorage = IsolatedStorageFile.GetUserStoreForApplication();

            using (appStorage)
            {
                using (IsolatedStorageFileStream isoFileStream = new IsolatedStorageFileStream(fileUri.Host, FileMode.Open, appStorage))
                {
                    byte[] buffer = new byte[isoFileStream.Length];
                    isoFileStream.Read(buffer, 0, buffer.Length);

                    stream.Write(buffer, 0, buffer.Length);
                    stream.Seek(0, SeekOrigin.Begin);
                }
            }

            return stream;
        }

        /// <summary>
        /// Either commits a temporary file as saved file or gets rid of it.
        /// </summary>
        /// <param name="slideIndex">Index of slide.</param>
        /// <param name="commitChanges">True to commit changes else false.</param>
        /// <returns>Uri of finally saved file.</returns>
        public Uri SetAudio(uint slideIndex, bool commitChanges)
        {
            Uri finalUri = null;
            string commitFileName, tempFileName;
            IsolatedStorageFile appStorage;

            if (commitChanges)
            {
                // remove previously committed file
                getFileStorageInfo(slideIndex, false, out commitFileName, out appStorage, ContentTypes.WAV);

                using (appStorage)
                {
                    appStorage.DeleteFile(commitFileName);
                }

                // save temp file as committed file
                getFileStorageInfo(slideIndex, true, out tempFileName, out appStorage, ContentTypes.WAV);

                using (appStorage)
                {
                    string[] files = appStorage.GetFileNames();

                    appStorage.MoveFile(tempFileName, commitFileName);
                }

                finalUri = new Uri(@"\\" + commitFileName);  // Uri does not accept simple file names
            }
            else
            {
                // remove temp file
                getFileStorageInfo(slideIndex, true, out tempFileName, out appStorage, ContentTypes.WAV);

                using (appStorage)
                {
                    appStorage.DeleteFile(tempFileName);
                }
            }

            return finalUri;
        }

        #endregion
    }

    /// <summary>
    /// Represents a locally created slide.
    /// </summary>
    public class LocalSlide : Slide
    {
        /// <summary>
        /// Uri of locally saved image.
        /// </summary>
        public Uri LocalImageSource { get; set; }

        /// <summary>
        /// Uri of locally saved media.
        /// </summary>
        public Uri LocalMediaSource { get; set; }
    }
}
